package fr.inserm.umr915.vcfannotator.annotators;
import fr.inserm.umr915.libjson.ArrayNode;
import fr.inserm.umr915.libjson.Node;
import fr.inserm.umr915.libjson.ObjectNode;
import fr.inserm.umr915.vcfannotator.AnnotationException;
import fr.inserm.umr915.vcfannotator.BaseDatabase;
import fr.inserm.umr915.vcfannotator.Variant;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.LockMode;
import com.sleepycat.je.OperationStatus;
import com.sleepycat.je.SecondaryCursor;

public class PositionalDelimiterAnnotator
	extends AbstractDelimitedAnnotator
	implements PositionalAnnotator
	{
	private int chromColumn=-1;
	private int chromStartColumn=-1;
	private int chromEndColumn=-1;
	private boolean positionsZeroBased=true;
	private String build=null;
	private ChromosomeNameConverter chromosomeNameConverter=new DefaultChromosomeNameConverter();
	private SecondaryCursor cursor=null;
	private DatabaseEntry key=new DatabaseEntry();
	private DatabaseEntry pKey=new DatabaseEntry();
	private DatabaseEntry data=new DatabaseEntry();
	private boolean arrayBased=true;
	private String propertyName=null;
	
	private Variant.Binding variantBinding=new Variant.Binding();
	
	public PositionalDelimiterAnnotator()
		{
		
		}
	

	@Override
	public boolean isPositionsZeroBased()
		{
		return positionsZeroBased;
		}
	
	public void setPositionsZeroBased(boolean positionsZeroBased)
		{
		this.positionsZeroBased = positionsZeroBased;
		}
	
	public int getChromColumn()
		{
		return chromColumn;
		}

	public void setChromColumn(int chromColumn)
		{
		this.chromColumn = chromColumn;
		}

	public int getChromStartColumn()
		{
		return chromStartColumn;
		}

	public void setChromStartColumn(int chromStartColumn)
		{
		this.chromStartColumn = chromStartColumn;
		}

	public int getChromEndColumn()
		{
		return chromEndColumn;
		}

	public void setChromEndColumn(int chromEndColumn)
		{
		this.chromEndColumn = chromEndColumn;
		}
	
	public ChromosomeNameConverter getChromosomeNameConverter()
		{
		return chromosomeNameConverter;
		}

	public void setChromosomeNameConverter(ChromosomeNameConverter chromosomeNameConverter)
		{
		this.chromosomeNameConverter = chromosomeNameConverter;
		}
	
	@Override
	public String getChromosome(String[] tokens)
		{
		return getChromosomeNameConverter().convertChromosome(tokens[getChromColumn()]);
		}
	@Override
	public int getChromStart(String[] tokens)
		{
		return Integer.parseInt(tokens[getChromStartColumn()]);
		}
	@Override
	public int getChromEnd(String[] tokens)
		{
		int end=getChromEndColumn();
		if(end==-1 || end==getChromStartColumn())
			{
			return getChromStart(tokens)+1;//half open interval
			}
		return Integer.parseInt(tokens[end]);
		}
	
	@Override
	public String getBuild() {
		return build;
		}
	
	public void setBuild(String build) {
		this.build = build;
		}
	
	@Override
	public void init(BaseDatabase baseDatabase)
		{
		super.init(baseDatabase);
		this.cursor=baseDatabase.getVariantDatabase().openCursor(null, null);
		}
	
	@Override
	public void cleanup() {
		if(this.cursor!=null)
			{
			this.cursor.close();
			this.cursor=null;
			}
		super.cleanup();
		}
	
	public void annotate(String tokens[]) throws AnnotationException
		{
		int shiftPosition=(isPositionsZeroBased()?1:0);
		String chrom= getChromosome(tokens);
		int chromStart1= getChromStart(tokens)+shiftPosition;
		int chromEnd1= getChromEnd(tokens)+shiftPosition;
		boolean first=true;
		Variant var=new Variant(
			getBuild(),
			chrom,
			chromStart1,
			"",""
			);
		
		variantBinding.objectToEntry(var, key);
		for(;;)
			{
			OperationStatus op=(first?
					cursor.getSearchKeyRange(key,pKey, data, LockMode.DEFAULT):
					cursor.getNext(key,pKey, data, LockMode.DEFAULT)
					);
			if(op!=OperationStatus.SUCCESS) break;
			first=false;
			var=variantBinding.entryToObject(key);
			if(var.getChrom().compareTo(chrom)<0) continue;
			if(var.getChrom().compareTo(chrom)>0) break;
			if(var.getPosition()< chromStart1) continue;
			if(var.getPosition()>=chromEnd1) break;
			ObjectNode annotation= nodeBinding.entryToObject(data).asObjectNode();
			if(!updateObject(annotation,tokens)) continue;
			
			nodeBinding.objectToEntry(annotation, data);
			if(baseDatabase.getJsonDatabase().put(null, pKey, data)!=OperationStatus.SUCCESS)
				{
				throw new RuntimeException("Cannot re-insert "+var+":"+annotation);
				}
			}
		
		}
	
	public boolean isArrayBased()
		{
		return arrayBased;
		}
	
	public void setArrayBased(boolean arrayBased) {
		this.arrayBased = arrayBased;
		}
	
	public String getPropertyName()
		{
		return propertyName;
		}
	
	public void setPropertyName(String propertyName)
		{
		this.propertyName = propertyName;
		}
	
	public boolean updateObject(ObjectNode annotation, String tokens[])
		{
		if(isArrayBased())
			{
			String key=getPropertyName();
			Node n= annotation.get(key);
			ArrayNode array=null;
			if(n==null)
				{
				array=nodeFactory.createArrayNode();
				annotation.put(key, array);
				}
			else
				{
				array=n.asArrayNode();
				}
			ObjectNode newnode=createAnnotation(tokens);
			if(newnode==null) return false;
			return (array.insert(newnode));
			}
		else
			{
			ObjectNode newnode=createAnnotation(tokens);
			if(newnode==null) return false;
			Node oldNode=annotation.get(getPropertyName());
			if(oldNode!=null && newnode.equals(oldNode)) return false;
			annotation.put(getPropertyName(),newnode);
			return true;
			}
		}
	
	
	public ObjectNode createAnnotation(String tokens[])
		{
		ObjectNode annotation= super.nodeFactory.createObjectNode(getFields().size());
		for(Field field: getFields())
			{
			field.annotate(annotation, tokens);
			}
		return annotation;
		}
	
	}
